//
// Created by LiuYou on 2021/11/30.
//

#include <iostream>
#include <random>


/*
 * 在C++11之前, C和C++都依赖于一个简单的C库函数: rand(); 来生成随机数。
 *
 * 而这个 rand(); 生成的随机数大多数时候不符合我们的业务要求。
 *
 * 在C++中应该使用定义在头文件 random 中的随机数库通过一组协作的类来解决随机数问题:
 * 1. 随机数引擎类(random-number engines), 一个引擎类可以生成 unsigned 随机数序列。
 * 2. 随机数分布类(random-number distribution), 一个分布类使用一个引擎类生成指定类型的、给定范围内的、服从特定概率分布的随机数。
 * 即
 * random-number engines: 生成随机 unsigned 整数序列。
 * random-number distribution: 使用引擎返回服从特定概率分布的随机数。
 *
 * C++程序不应该使用 rand(); 库函数, 而是应该使用 default_random_engine类 和恰当的 分布类 对象。
 */

int main(int argc, char** argv) {
    {
        /*
         * 随机数引擎是函数对象类, 这些类定义了一个调用运算符重载(即()运算符重载), 该调用运算符重载函数不接受参数, 返回一个随机 unsigned 整数。
         * 我们可以通过一个随机数引擎对象来生成原始随机数:
         */
        // Clang-Tidy: Random number generator seeded with a default argument will generate a predictable sequence of values
        // Clang-Tidy: 带有默认参数的随机数生成器将生成可预测的值序列。
        // Clang-Tidy: Random number generator seeded with a constant value will generate a predictable sequence of values
        // Clang-Tidy：以恒定值作为种子的随机数生成器将生成可预测的值序列
        std::default_random_engine defaultRandomEngine( 10 );
        for (int i = 0; i < 10; ++i) {
            std::cout << i << ": " << defaultRandomEngine() << std::endl;
        }
        std::cout << std::endl;
        /*
         * todo 笔记: 注意: 对于大多数场合, 随机数引擎的输出是不能直接使用的, 这也是为什么称这些输出为原始随机数。
         *  问题出在: 生成的随机数的值范围通常与我们需要的不符, 而正确转换随机数的范围是极其困难的！
         *  所以才有了分布类型 -> 为了得到在一个指定范围内的数, 我们需要使用一个分布类型的对象。
         */
    }

    {
        /*
         * 使用分布类型和配合随机数引擎。
         */

        // 均匀的int分布。在 [0, 9] 均匀分布。
        std::uniform_int_distribution<> uniformIntDistribution( 0, 9 );
        std::default_random_engine defaultRandomEngine( 8514515616 );
        for (int i = 0; i < 10; ++i) {
            std::cout << uniformIntDistribution( defaultRandomEngine ) << " ";
        }
        std::cout << std::endl;

        /*
         * todo 笔记: 注意: 当我们说随机数生成器时, 是指 分布对象 和 引擎对象 的组合。
         */
    }

    {
        /*
         * 通过上面的两个例子, 我知道了引擎对象是需要一个种子(seed)的。而必须尽量让这个种子不同。
         * 所以一般比较常用的方法是调用系统函数: time(); 如下:
         */
        std::default_random_engine randomEngine( time( nullptr ));
        std::uniform_int_distribution<> uniformIntDistribution( 0, 5 );
        for (int i = 0; i < 10; ++i) {
            std::cout << uniformIntDistribution( randomEngine ) << " " << time( nullptr ) << std::endl;
        }
        std::cout << time( nullptr ) << std::endl;
        std::cout << std::endl;
        /*
         * todo 笔记: 虽然使用了 time(nullptr); 来返回一个跟时间相关的数, 每次生成序列是不同的。但是！！！！
         *  但是: 生成的第一个数永远是 0 ！！！！ 这不是我想要的。
         */
    }

    {
        /*
         * 生成随机数的种子。
         *
         * 获取随机种子, random_device类 定义的函数对象可以生成用来作为 seed 种子的随机的 unsigned int 。
         * 可以按如下方式创建 random_device 对象:
         * // source of seeds.(种子的来源。)
         * std::random_device randomDevice;
         */
        std::random_device randomDevice;
        for (int i = 0; i < 10; ++i) {
            std::cout << randomDevice() << " ";
        }
        std::cout << std::endl;
        // 运行结果:
        // 第一次运行结果:
        // 1940234769 2050945481 4251951281 1446750763 2667823940 3725386545 797725785 1779001124 2832966779 1268387812
        // 第二次运行结果:
        // 1086070340 1419052981 2129538718 1563801877 3500451628 3635316608 1166254100 3470990621 4236256123 2536552149
        // 第三次:
        // 1862379536 306322246 3643315832 436651068 988378442 2829049483 1459634722 2995461276 2912336941 1201554382
        // 第四次:
        // 1176646636 2740057656 875212971 3074516158 271862523 416237277 1876762906 2113691527 2729921191 3520196790
        // 第五次:
        // 2031717981 4143167456 1212532557 2047745761 1225671537 797867652 242144880 4047412427 3494800681 1911998212

    }

    {
        /*
         * 种子序列。
         *
         * seed_seq类 是用来帮助设置随机数生成器的初始状态的, 即 seed_seq类 为随机数生成器生成种子序列。
         *
         * 这个类应该使用的不多。一般都是使用 std::random_device类 就可以了。
         */
        std::seed_seq seeds;
    }

    {
        /*
         * 综合:
         */
        std::random_device randomDevice;
        std::default_random_engine randomEngine( randomDevice());
        std::uniform_int_distribution<> uniformIntDistribution( 0, 5 );
        for (int i = 0; i < 10; ++i) {
            std::cout << uniformIntDistribution( randomEngine ) << " ";
        }
        std::cout << std::endl;
        /*
         * todo 笔记: 这个版本生成的随机数第一个元素不会总是 0, 而且也比较均匀, 是我想要的版本。
         */
    }

    {
        /*
         * 最后的一个补充:
         *
         * 创建标准均匀分布。
         *
         * 标准均匀分布是一个在范围 [0, 1) 内的连续分布。
         * // 生成规范
         * generate_canonical();函数模板提供了一个浮点值范围在 [0, 1) 内, 且有给定的随机比特数的标准均匀分布。
         * 函数原型为:
         * template<typename _RealType, size_t __bits, typename _UniformRandomNumberGenerator>
         * _RealType generate_canonical(_UniformRandomNumberGenerator& __g);
         *
         * 它有 3 个模板参数:
         * 浮点类型
         * 位数的随机比特的个数
         * 使用的随机数生成器的类型
         * 它有 1 个参数:
         * 一个随机数生成器, 所以最后一个模板参数可以根据参数推导出来。
         * 它的返回值类型为即模板参数的第一个, 一般使用的是 double类型。
         *
         * 一个函数模板，用于将 (整数)均匀随机数生成器(例如: std::default_random_engine对象) 的输出 转换为范围[0-1)内的浮点结果。
         *
         * 用法如下:
         */
        std::random_device randomDevice;
        std::default_random_engine randomEngine( randomDevice());
        for (int i = 0; i < 10; ++i) {
            // todo 笔记: 下面这行代码会返回一个有 10 个随机比特的 double类型 的随机值。
            std::cout << std::generate_canonical<double, 10>( randomEngine ) << " ";
//            std::cout << std::generate_canonical<double, 1>( randomEngine ) << " ";
        }
        std::cout << std::endl;
        // 第一次运行结果:
        // 0.110223 0.513578 0.706303 0.839707 0.960982 0.229334 0.411414 0.636232 0.145748 0.585027
        // 第二次
        // 0.447888 0.657352 0.108738 0.555662 0.0137286 0.736283 0.705485 0.0820537 0.0762547 0.613265
        // 第三次
        // 0.284906 0.409343 0.831014 0.845838 0.997505 0.0661883 0.426524 0.593994 0.254752 0.61767
        // 第四次
        // 0.174595 0.417145 0.953472 0.00381891 0.184399 0.188225 0.493956 0.91251 0.555776 0.921281
    }

    return 0;
}
